This example mirrors cifti.4D.html.

In [1]:
from pathlib import Path

from ipyniivue import download_dataset

BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")

# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii",
        "Conte69.L.inflated.32k_fs_LR.surf.gii",
    ],
)
Downloading Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii...
Downloading Conte69.L.inflated.32k_fs_LR.surf.gii...
Dataset downloaded successfully to images.
In [2]:
import math

import ipywidgets as widgets

import ipyniivue

# Initialize NiiVue widget
v = ipyniivue.NiiVue()
v.opts.show_3d_crosshair = True
v.opts.back_color = [0.9, 0.9, 1, 1]
v.set_slice_type(ipyniivue.SliceType.RENDER)

# Define Mesh and Layers
mesh_layer_config = {
    "path": DATA_FOLDER / "Conte69.MyelinAndCorrThickness.32k_fs_LR.dtseries.nii",
    "cal_min": 0.01,
    "cal_max": 3.5,
    "colormap": "rocket",
    "opacity": 0.7,
}

mesh_config = {
    "path": DATA_FOLDER / "Conte69.L.inflated.32k_fs_LR.surf.gii",
    "rgba255": [255, 255, 255, 255],
    "layers": [mesh_layer_config],
}

# Load the mesh
v.load_meshes([mesh_config])

# Set Clip Plane
v.set_clip_plane(-0.1, 270, 0)

# --- UI Controls ---

# Timepoint Slider
timepoint_slider = widgets.IntSlider(
    value=0, min=0, max=1, description="Timepoint:", continuous_update=True
)


def on_timepoint_change(change):
    """Handle timepoint change."""
    if v.meshes and v.meshes[0].layers:
        v.meshes[0].layers[0].frame_4d = change["new"]


timepoint_slider.observe(on_timepoint_change, names="value")

# Opacity Slider
opacity_slider = widgets.FloatSlider(
    value=0.7,
    min=0.1,
    max=1.0,
    step=0.1,
    description="Opacity:",
    continuous_update=True,
)


def on_opacity_change(change):
    """Handle opacity change."""
    if v.meshes and v.meshes[0].layers:
        v.meshes[0].layers[0].opacity = change["new"]


opacity_slider.observe(on_opacity_change, names="value")

# Shader Buttons
shader_names = v.mesh_shader_names()


def create_shader_button(name):
    """Create a shader button."""
    btn = widgets.Button(description=name)

    def on_click(b):
        if v.meshes:
            v.set_mesh_shader(v.meshes[0].id, name)

    btn.on_click(on_click)
    return btn


shader_buttons = [create_shader_button(name) for name in shader_names]

# Organize shader buttons in a grid
num_cols = 6
num_rows = math.ceil(len(shader_buttons) / num_cols)
shader_grid = []

for i in range(num_rows):
    row_buttons = shader_buttons[i * num_cols : (i + 1) * num_cols]
    shader_grid.append(widgets.HBox(row_buttons))

shader_buttons_widget = widgets.VBox(shader_grid)

# Display Layout
controls_header = widgets.HBox([timepoint_slider, opacity_slider])

display(
    widgets.VBox([controls_header, v, widgets.Label("Shaders:"), shader_buttons_widget])
)